package com.xuzhiguang.lightnat.client.transfer;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import com.xuzhiguang.lightnat.client.Client;
import com.xuzhiguang.lightnat.client.common.handler.ExceptionHandler;
import com.xuzhiguang.lightnat.client.common.handler.IdleCheckHandler;
import com.xuzhiguang.lightnat.client.common.handler.KeepAliveHandler;
import com.xuzhiguang.lightnat.client.transfer.handler.InactiveHandler;
import com.xuzhiguang.lightnat.client.transfer.handler.WritabilityChangedHandler;
import com.xuzhiguang.lightnat.common.codec.NatFrameDecoder;
import com.xuzhiguang.lightnat.common.codec.NatFrameEncoder;
import com.xuzhiguang.lightnat.common.codec.NatMessageDecoder;
import com.xuzhiguang.lightnat.common.codec.NatMessageEncoder;
import com.xuzhiguang.lightnat.common.manager.ProxyChannelManager;
import com.xuzhiguang.lightnat.common.message.MessageProcessorFactory;
import com.xuzhiguang.lightnat.common.message.MessageProcessorHandler;
import com.xuzhiguang.lightnat.common.message.NatMessage;
import com.xuzhiguang.lightnat.common.message.transfer.TransferAuthenticationRequest;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Data
public class TransferClient implements Client {

    private final TransferClientProperties transferClientProperties;

    private MessageProcessorFactory messageProcessorFactory;

    private EventLoopGroup group;

    private Bootstrap bootstrap;

    private final ConcurrentHashMap<String, Channel> channels = new ConcurrentHashMap<>();

    public TransferClient(TransferClientProperties transferClientProperties) {
        this.transferClientProperties = transferClientProperties;
    }

    @Override
    public void start() {
        group = new NioEventLoopGroup(new DefaultThreadFactory("t-client"));

        bootstrap = new Bootstrap();
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
        KeepAliveHandler keepAliveHandler = new KeepAliveHandler();
        InactiveHandler inactiveHandler = new InactiveHandler();
        WritabilityChangedHandler writabilityChangedHandler = new WritabilityChangedHandler();
        ExceptionHandler exceptionHandler = new ExceptionHandler();
        MessageProcessorHandler messageProcessorHandler
                = new MessageProcessorHandler(this.messageProcessorFactory);

        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(loggingHandler)
                .handler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast("idleCheckHandler", new IdleCheckHandler())
                                .addLast("natFrameDecoder", new NatFrameDecoder())
                                .addLast("natFrameEncoder", new NatFrameEncoder())
                                .addLast("natMessageDecoder", new NatMessageDecoder())
                                .addLast("natMessageEncoder", new NatMessageEncoder())
                                .addLast("keepAliveHandler", keepAliveHandler)
                                .addLast("inactiveHandler", inactiveHandler)
                                .addLast("writabilityChangedHandler", writabilityChangedHandler)
                                .addLast("messageProcessorHandler", messageProcessorHandler)
                                .addLast("exceptionHandler", exceptionHandler);
                    }
                });
    }

    @Override
    public void stop() {
        this.channels.forEach((s, channel) -> channel.close());
        this.group.shutdownGracefully();
    }

    public boolean connect() {
        ChannelFuture f = null;
        try {
            f = bootstrap.connect(transferClientProperties.getTransferServerHost(), transferClientProperties.getTransferServerPort()).sync();
        } catch (InterruptedException e) {
            log.info("transfer client connect fail.", e);
            Thread.currentThread().interrupt();
            return false;
        }

        if (f.isSuccess()) {
            Channel channel = f.channel();
            this.channels.put(channel.id().asLongText(), channel);
            this.auth(channel);
        }
        return f.isSuccess();
    }

    public void disconnect(Channel channel) {
        this.channels.remove(channel.id().asLongText());
        ProxyChannelManager.remove(channel);
        channel.close();
    }

    private void auth(Channel channel) {
        TransferAuthenticationRequest request = new TransferAuthenticationRequest();
        request.setToken(this.transferClientProperties.getTransferToken());
        NatMessage natMessage = new NatMessage(request);
        channel.writeAndFlush(natMessage);
    }

    private void checkProperties() {

        if (StrUtil.isEmptyIfStr(transferClientProperties.getTransferServerHost())) {
            throw new IllegalArgumentException("transfer server host is null");
        }

        if (!NetUtil.isValidPort(transferClientProperties.getTransferServerPort())) {
            throw new IllegalArgumentException("transfer server port is illegal. server port:" + transferClientProperties.getTransferServerPort());
        }

        if (StrUtil.isEmptyIfStr(transferClientProperties.getTransferToken())) {
            throw new IllegalArgumentException("transfer token is null");
        }
    }

}
